using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;
using System.Text.RegularExpressions;

using System.IO;
using System.Linq;

namespace Neznayka
{
    /// <summary>
    /// This is a SQL rule which returns a warning message 
    /// whenever there is a ........................................
    /// This rule only applies to .................................
    /// </summary>

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
    [DataRuleAttribute(
        NeznaykaConstants.NameSpace,
        NeznaykaConstants.EnforceClusteredIndexIsPrimaryOrForeignKeyRuleId,
        NeznaykaConstants.ResourceBaseName,
        NeznaykaConstants.EnforceClusteredIndexIsPrimaryOrForeignKey_RuleName,
        NeznaykaConstants.CategoryDatabaseStructures,
        DescriptionResourceId = NeznaykaConstants.EnforceClusteredIndexIsPrimaryOrForeignKey_ProblemDescription)]
    [SupportedElementType(typeof(ISqlTable))]
    public class EnforceClusteredIndexIsPrimaryOrForeignKeyRule : StaticCodeAnalysisRule
    {
        #region Overrides
        /// <summary>
        /// Analyze the model element
        /// </summary>
        public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context)
        {
            // (Re)-Load Environment settings

            List<DataRuleProblem> problems;
            SqlSchemaModel sqlSchemaModel;
            ISqlModelElement sqlElement;
            TSqlFragment sqlFragment;
            DMVRuleSetup.RuleSetup(context, out problems, out sqlSchemaModel, out sqlElement, out sqlFragment);

            // Refresh cached index/constraints/tables lists from Model
            DMVRuleSetup.RefreshDDLCache(sqlSchemaModel);

            string owningObjectSchema;
            string owningObjectTable;
            DMVRuleSetup.getOwningObject(sqlElement, out owningObjectSchema, out owningObjectTable);


            List<ISqlPrimaryKeyConstraint>  pks                     = ModelIndexAndKeysUtils.getPrimaryKeys(owningObjectSchema, owningObjectTable);
            List<ISqlPrimaryKeyConstraint>  clusteredpks            = ModelIndexAndKeysUtils.getClusteredPrimaryKeys(owningObjectSchema, owningObjectTable);
            List<ISqlForeignKeyConstraint>  foreignkeyconstraints   = ModelIndexAndKeysUtils.getForeignKeys(owningObjectSchema, owningObjectTable);
            List<ISqlIndex>                 clusteredindexes        = ModelIndexAndKeysUtils.getClusteredIndexes(owningObjectSchema, owningObjectTable) ;
            List<ISqlUniqueConstraint>      uniqueClusterConstraints = ModelIndexAndKeysUtils.getClusteredUniqueConstraints(owningObjectSchema, owningObjectTable) ;


            bool clusteredindexExists                   = (clusteredindexes.Count > 0);
            bool clusteredUniqueConstraintExists        = (uniqueClusterConstraints.Count > 0);
            bool clusteredPrimaryKeyExists              = (clusteredpks.Count > 0);

            bool primaryKeyExists                       = (pks.Count > 0);
            bool foreignKeyExists                       = (foreignkeyconstraints.Count > 0);

            List<TSqlFragment> issues                   = new List<TSqlFragment>();
            bool foundKeyThatMatchesACluster            = false;

            // only if all these conditions are true do we need to check for rule violations.
            if (   (DMVSettings.AllowClusterOnPrimaryKey || DMVSettings.AllowClusterOnForeignKey)
                && (primaryKeyExists                            || foreignKeyExists)
                && (clusteredPrimaryKeyExists                   || clusteredindexExists     || clusteredUniqueConstraintExists)
                )
            {
                if (DMVSettings.AllowClusterOnPrimaryKey)
                {
                    if (clusteredpks.Count > 0)
                    {
                        foundKeyThatMatchesACluster = true;
                    }
                }
                if (DMVSettings.AllowClusterOnForeignKey && !foundKeyThatMatchesACluster)
                {
                    // try to find a foreign key that we might be clustering on, to tick it off as OK.
                    if (foreignkeyconstraints.Count > 0)
                    {

                        bool match = false;
                        if (clusteredindexExists || clusteredUniqueConstraintExists || clusteredPrimaryKeyExists)
                        {
                            ISqlIndex clusteredindex = null;
                            ISqlUniqueConstraint uniqueConstraint = null;
                            ISqlPrimaryKeyConstraint primaryKeyConstraint = null;

                            List<String> LeadingEdgeIndexColumns = new List<String>();
                            List<String> SortedLeadingEdgeIndexColumns = new List<String>();

                            if (clusteredindexExists)
                            {
                                clusteredindex = clusteredindexes[0];
                                foreach (var c in clusteredindex.ColumnSpecifications)
                                {
                                    /* so why won't this work
                                    List<String> names = EnumerateColumnSpecificationNameParts.getColumnNameParts(c);
                                    String lastElement = names[names.Count - 1];
                                    LeadingEdgeIndexColumns.Add(lastElement);
                                    */
                                    String lastElement = "";
                                    foreach (var n in c.Column.Name.Parts)
                                    {
                                        lastElement = n;
                                    }
                                    LeadingEdgeIndexColumns.Add(lastElement);
                                }

                                SortedLeadingEdgeIndexColumns =
                                    LeadingEdgeIndexColumns.OrderBy(col => col, SqlComparer.Comparer).Select(n => n).ToList();
                            }
                            else if (clusteredUniqueConstraintExists)
                            {
                                uniqueConstraint = uniqueClusterConstraints[0];
                                foreach (var c in uniqueConstraint.ColumnSpecifications)
                                {
                                    String lastElement = "";
                                    foreach (var n in c.Column.Name.Parts)
                                    {
                                        lastElement = n;
                                    }
                                    LeadingEdgeIndexColumns.Add(lastElement);
                                }

                                SortedLeadingEdgeIndexColumns =
                                    LeadingEdgeIndexColumns.OrderBy(col => col, SqlComparer.Comparer).Select(n => n).ToList();
                            }
                            else if (clusteredPrimaryKeyExists)
                            {
                                primaryKeyConstraint = clusteredpks[0];
                                foreach (var c in primaryKeyConstraint.ColumnSpecifications)
                                {
                                    String lastElement = "";
                                    foreach (var n in c.Column.Name.Parts)
                                    {
                                        lastElement = n;
                                    }
                                    LeadingEdgeIndexColumns.Add(lastElement);
                                }

                                LeadingEdgeIndexColumns.OrderBy(col => col, SqlComparer.Comparer).Select(n => n).ToList();
                            }


                            // now check the foreign key columns againt the relevant clustered 'index''s columns
                            foreach (var fc in foreignkeyconstraints)
                            {
                                // consider a foreign key to be clustered if all its columns appear as the first n columns in a
                                // clustered index, clustered unique constraint or clustered primary key constraint.
                                // nb a primary key can be a foreign key too when modelling 1:1 relationships.
                                List<String> SortedForeignKeyColumns = fc.Columns.OrderBy(col => col.Name.Parts[2], SqlComparer.Comparer).Select(n => n.Name.Parts[2]).ToList();
                                if (SortedLeadingEdgeIndexColumns.Count >= SortedForeignKeyColumns.Count)
                                {
                                    List<String> leadingCols = SortedLeadingEdgeIndexColumns.Take(SortedForeignKeyColumns.Count).ToList();
                                    if ( Enumerable.SequenceEqual( leadingCols, SortedForeignKeyColumns, SqlComparer.Comparer))
                                    {
                                        match = true;
                                        break;
                                    }
                                }
                            }
                        }
                        foundKeyThatMatchesACluster = match;
                    }
                }
            }
            // only if all these conditions are true do we need to check for rule violations.
            // otherwise by default it's a trivial success
            else
            {
                foundKeyThatMatchesACluster = true;
            }


            if (!foundKeyThatMatchesACluster)
            {
                issues.Add(sqlFragment);
            }


            // Create problems for each object
            foreach (TSqlFragment issue in issues)
            {
                DataRuleProblem problem = new DataRuleProblem(this,
                                            String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                            sqlElement);

                SqlRuleUtils.UpdateProblemPosition(problem, issue.StartOffset, issue.FragmentLength);
                problems.Add(problem);
            }


            return problems;
        }


        #endregion    
 
    }
}
